使用protobuf (proto3, C++和go语言) |
您所在的位置:网站首页 › c语言 uint128输出 › 使用protobuf (proto3, C++和go语言) |
在这里,我先讲述C++使用protobuf,之后,会补充使用go语言使用protobuf。 使用protobuf需要有如下步骤: 在.proto文件中定义消息(message)格式。 使用protobuf的编译器编译.proto文件成为相应的语言代码。 使用对应语言的protobuf API读写消息。 在这里,我直接使用了官方的示例,之后打算使用grpc简单转写这个示例。官方示例实现了一个称为addressbook的功能,具体包括两部分,第一部分是向addressbook中添加个人信息,第二部分是,读取个人信息。在这里实现的第一步是在.proto中定义个人的结构,当然,如果你想采取自顶向下设计的话,可能会先定义对用户接口。下面我们看一下定义的.proto的文件的源代码: // [START declaration] syntax = "proto3"; package tutorial; import "google/protobuf/timestamp.proto"; // [END declaration] // [START messages] message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5; } // Our address book file is just one of these. message AddressBook { repeated Person people = 1; } // [END messages]这里,我们对.proto文件所使用的语法进行简单讲解。 1)protobuf使用的.proto文件以包声明开始,包声明和C++中的namespace对应,在某个包声明中定义的消息,会出现在对应的namespace命名空间中。import语句用来导入其他.proto文件中的消息定义,这样就可以在多个.proto文件中定义消息,然后关联使用了。 2)然后,你需要定义消息结构。一个消息包括多个带类型的成员。protobuf有许多标准的简单数据类型,包括bool, int32, float,double以及string, protobuf自带的.proto文件中也有一些消息结构定义,例如上面出现的google.protobuf.Timestamp。当然,你也可以根据这些类型,进一步构造其他消息,例如上面的Person包含了PhoneNumber消息,AddressBook包含了Person消息。你也可以在其他消息中定义消息类型,例如上面出现在PhoneNUmber在Person中进行定义。你还可以定义enum类型,例如上面的PhoneType,包含MOBILE,HOME和WORK三个可选值。 “=1”, “=2”是用来在二进制编码中标识对应字段的tag。tag在1-15范围内只需要一个byte来编码,而较大的数字需要两个byte来编码,所以对于常用的那些字段,可以使用1-15范围内的tag。 另外,每一个tag可以使用如下修饰符修饰: (1)singular: 表示这个字段可以有一个,也可以没有。如果没有的话,在编码的时候,不会占用空间。 (2)repeated: 表示这个字段会重复0次或者更多次,这个字段里的值会按照顺序编码。 2. 定义完了.proto文件,下一步就是编译这个proto文件,我们假设这个proto文件名为addressbook.proto。为了编译这个文件,运行如下的语句: protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/address.proto其中-I指定proto文件所在的位置,$DST_DIR指定生成文件所在的位置,这里--cpp_out表示生成文件为C++文件,生成目录在$DST_DIR,$SRC_DIR/addressbook.proto。 如果你在proto所在文件调用上述命令,可以简写如下: protoc --cpp_out=. addressbook.proto调用上述命令,生成的文件为addressbook.pb.h和addressbook.pb.cc。可以推测,对于xxx.proto,生成文件应该为xxx.pb.h和xxx.pb.cc。
下面简单查看一些类的定义: class Person_PhoneNumber : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition::tutorial.Person.PhoneNumber) */ { public: Person_PhoneNumber(); virtual ~Person_PhoneNumber(); static const ::google::protobuf::Descriptor* descriptor() { return default_instance().GetDescriptor(); } // accessors ---------------------------------------------------------------- // string number = 1; void clear_number(); const ::std::string& number() const; void set_number(const ::std::string& value); void set_number(::std::string&& value); void set_number(const char* value); void set_number(const char* value, size_t size); ::std::string* mutable_number(); ::std::string* release_number(); void set_allocated_number(::std::string* number); // .tutorial.Person.PhoneType type = 2; void clear_type(); ::tutorial::Person_PhoneType type() const; void set_type(::tutorial::Person_PhoneType value); };这里的descriptor函数,可以用于反射处理。proto文件在编译时,会提供比较详细的操作和获取函数,当做普通类处理,也会很方便。另外注意这个函数的命令Person_PhoneNumber。在proto文件中,Person为外部类,PhoneNumber是内嵌在Person中的类,对应生成的类名就是按照上面的规则。注意下mutable_number方法,这个方法在没有设置number的时候也可以调用,在调用时,number会被初始化为空字符串。 enum Person_PhoneType { Person_PhoneType_MOBILE = 0, Person_PhoneType_HOME = 1, Person_PhoneType_WORK = 2, ... }; class Person : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition: tutorial.Person) */ { public: Person(); virtual ~Person(); static const ::google::protobuf::Descriptor* descriptor() { return default_instance().GetDescriptor(); } typedef Person_PhoneNumber PhoneNumber; typedef Person_PhoneType PhoneType; static const PhoneType MOBILE = Person_PhoneType_MOBILE; static const PhoneType HOME = Person_PhoneType_HOME; static const PhoneType WORK = Person_PhoneType_WORK; static inline bool PhoneType_IsValid(int value) { return Person_PhoneType_IsValid(value); } static inline const ::std::string& PhoneType_Name(PhoneType value) { return Person_PhoneType_Name(value); } static inline bool PhoneType_Parse(const ::std::string& name, PhoneType* value) { return Person_PhoneType_Parse(name, value); } // accessors ------------------------------------------- // repeated .tutorial.Person.PhoneNumber phones = 4; int phones_size() const; void clear_phones(); ::tutorial::Person_PhoneNumber* mutable_phones(int index); ::google::protobuf::RepeatedPtrField* mutable_phones(); const ::tutorial::Person_PhoneNumber& phones(int index) const; ::tutorial::Person_PhoneNumber* add_phones(); const ::google::protobuf::RepeatedPtrField& phones() const; // string name = 1; // string email = 3; // .google.protobuf.Timestamp last_updated = 5; bool has_last_updated() const; void clear_last_updated(); const ::google::protobuf::Timestamp& last_updated() const; ::google::protobuf::Timestamp* release_last_updated(); ::google::protobuf::Timestamp* mutable_last_updated(); void set_allocated_last_updated(::google::protobuf::Timestamp* last_updated); // int32 id = 2; void clear_id(); ::google::protobuf::int32 id() const; void set_id(::google::protobuf::int32 value); };这个类的定义和上面的Person_PhoneNumber没有太大的差别,其中的typedef类型重定义和const定义,通过这种方式,来使得PhoneNumber一类的内嵌类使用起来更加自然,更符合.proto文件中的定义。可以查看一下不同类型的成员的不同操作方法。同一个类型的成员,提供的操作方法基本相同。另外注意一点,Person_PhoneNumber和Person类都继承于::google::protobuf::Message。
标准的Message方法 每一个消息类都有很多别的方法,让你来检查或者操作整个消息,消息类有这些方法,因为继承于Message类,或者直接使用下面的方法,或者重写了虚函数。 1) bool IsInitialialized() const; : 检查是不是所有必需的字段都已经设置, 这个函数是虚函数。 2) string DebugString() const; : 返回一个可读的消息表示,很适合用于调试。这个函数的实现如下: string Message::DebugString() const { string debug_string; TextFormat::Printer printer; printer.SetExpandAny(true); printer.PrintToString(*this, &debug_string); return debug_string; }输出的大致内容可以参考下面的函数: void TextFormat::Printer::Print(const Message& message, TextGenerator* generator) const { const Descriptor* descriptor = message.GetDescriptor(); auto itr = custom_message_printers_.find(descriptor); if (itr != custom_message_printers_.end()) { itr->second->Print(message, single_line_mode_, generator); return; } const Reflection* reflection = message.GetReflection(); if (descriptor->full_name() == internal::kAnyFullTypeName && expand_any_ && PrintAny(message, generator)) { return; } std::vector fields; if (descriptor->options().map_entry()) { fields.push_back(descriptor->field(0)); fields.push_back(descriptor->field(1)); } else { reflection->ListFields(message, &fields); } if (print_message_fields_in_index_order_) { std::sort(fields.begin(), fields.end(), FieldIndexSorter()); } for (int i = 0; i < fields.size(); i++) { PrintField(message, reflection, fields[i], generator); } if (!hide_unknown_fields_) { PrintUnknownFields(reflection->GetUnknownFields(message), generator); } }1) void CopyFrom(const Person& from); : 使用from的值来覆盖现有值,这个函数是虚函数。 2) void Clear(); 清理所有的元素,将消息重置为空值状态,这个函数是虚函数。
消息的解析和序列号 每一个消息类都有方法用protobuf二进制格式写入到string或者输出流,也可以从string或者输入流读取数据,来设置值。这些方法都是来自于Message类(或者间接来自于MessageLite)。这些方法包括: 1)bool SerializeToString(string* output) const; :将消息转化成protobuf二进制存储到string中,注意存储的是二进制,而不是文本。 2)bool ParseFromString(const string& data); : 从给定的string中解析消息。 3)bool SerializeToOstream(ostream* output) const; : 将消息写入到给定的C++ ostream中。 4)bool ParseFromIstream(istream* input); : 从C++ istream中解析消息。 还有一些用于解析和序列号的函数,可以自行查看。
3. 使用proto文件编译生成的源码和protobuf官方提供的API接口进行操作 我们先查看一下添加个人的应用: #include #include #include #include #include #include "addressbook.pb.h" using namespace std; using google::protobuf::util::TimeUtil; // This function fills in a Person message based on user input. void PromptForAddress(tutorial::Person* person) { cout > id; person->set_id(id); cin.ignore(256, '\n'); cout mutable_name()); cout set_email(email); } while (true) { cout add_phones(); phone_number->set_number(number); cout set_type(tutorial::Person::MOBILE); } else if (type == "home") { phone_number->set_type(tutorial::Person::HOME); } else if (type == "work") { phone_number->set_type(tutorial::Person::WORK); } else { cout |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |